﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.DotNet.Types;

namespace AR2HBMTask {
	/// <summary>
	/// Based on http://blog.aelogic.net/archive/2007/11/19/NAntbased-FTP-deployment-task.aspx
	/// </summary>
	[TaskName("ar2hbm")]
	public class AR2HBMTask : Task {
		private string _dialect = "NHibernate.Dialect.MsSql2005Dialect";
		private string _driver = "NHibernate.Driver.SqlClientDriver";

		[TaskAttribute("outputPath")]
		public string OutputPath { get; set; }

		[TaskAttribute("dialect")]
		public string Dialect {
			get { return _dialect; }
			set { _dialect = value; }
		}

		[TaskAttribute("driver")]
		public string Driver {
			get { return _driver; }
			set { _driver = value; }
		}

		[BuildElement("assemblies")]
		public AssemblyFileSet Assemblies { get; set; }

		private Assembly GetAssembly(Assembly refe, string asmName) {
			return Assembly.LoadFrom(Path.Combine(Path.GetDirectoryName(refe.Location), asmName));
		}

		protected override void ExecuteTask() {
			var properties = new Hashtable {
				{"hibernate.connection.driver_class", Driver},
				{"hibernate.dialect", Dialect}
			};

			var assemblies = new List<Assembly>();
			Assembly castle = null;
			Assembly nhibernate = null;
			if (Assemblies.FileNames.Count == 0)
				throw new ApplicationException("No assemblies found.");

			foreach (string asmFile in Assemblies.FileNames) {
				var asm = Assembly.LoadFrom(asmFile);
				if (castle == null) {
					castle = (from a in asm.GetReferencedAssemblies()
					          where a.Name == "Castle.ActiveRecord"
					          select GetAssembly(asm, "castle.activerecord.dll")).FirstOrDefault();
				}
				if (nhibernate == null) {
					nhibernate = (from a in asm.GetReferencedAssemblies()
					              where a.Name == "NHibernate"
												select GetAssembly(asm, "NHibernate.dll")).FirstOrDefault();
				}
				assemblies.Add(asm);
			}
			if (castle == null)
				throw new ApplicationException("Castle.ActiveRecord assembly not found in references.");
			if (nhibernate == null)
				throw new ApplicationException("NHibernate assembly not found in references.");
			var arStarter = Activator.CreateInstance(castle.GetType("Castle.ActiveRecord.ActiveRecordStarter", true));
			var inPlaceCfg =
				Activator.CreateInstance(castle.GetType("Castle.ActiveRecord.Framework.Config.InPlaceConfigurationSource", true));

			var arBase = castle.GetType("Castle.ActiveRecord.ActiveRecordBase", true);
			inPlaceCfg.GetType().InvokeMember("Add",
			                                  BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
			                                  null, inPlaceCfg, new object[] {arBase, properties});
			inPlaceCfg.GetType().InvokeMember("SetDebugFlag",
			                                  BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
			                                  null, inPlaceCfg, new object[] {true});

			string srcPath = AppDomain.CurrentDomain.BaseDirectory;
			const string allHbmXml = "*.hbm.xml";
			const string hibernateCfg = "hibernate.cfg.xml";
			foreach (var hbm in Directory.GetFiles(srcPath, allHbmXml))
				File.Delete(hbm);
			if (File.Exists(hibernateCfg))
				File.Delete(hibernateCfg);
			arStarter.GetType().InvokeMember("Initialize", BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Static,
			                                 null, arStarter, new[] {assemblies.ToArray(), inPlaceCfg});

			var cfg = new XElement("hibernate-configuration",
			                       new XElement("session-factory",
			                                    from f in Directory.GetFiles(srcPath, allHbmXml)
			                                    select new XElement("mapping", new XAttribute("resource", Path.GetFileName(f)))));
			var dstPath = OutputPath ?? srcPath;
			cfg.Save(Path.Combine(dstPath, hibernateCfg));
			try {
				foreach (var hbm in Directory.GetFiles(srcPath, allHbmXml))
					File.Move(hbm, Path.Combine(dstPath, Path.GetFileName(hbm)));
			} catch {
			}
		}
	}
}